#include "window.h"
#include "input.h"
#include "states.h"

#include "../Epsilon/Epsilon3D/OpenGL.h"

#include "../Epsilon/epsilon.h"
#include "../Epsilon/Epsilon3D/rendercontext.h"

#include "font.h"
#include "console.h"
#include "command.h"

bool
CALLBACK
WindowMove (eWindow* window, eWindowRect& rect)
{
#if 0
  eTB_Printf (_T ("Window: %s has moved to: (%d, %d)  [Client: (%d x %d)]\n"), window->title, rect.rcWindow.left,
                                                                                              rect.rcWindow.top,
                                                                                              rect.rcClient.left,
                                                                                              rect.rcClient.top);
#endif
  return true;
};

bool
CALLBACK
WindowResize (eWindow* window, eWindowRect& rect)
{
  eTB_Printf (_T ("Window: %s has been resized to: (%d x %d)  [Client: (%d x %d)]\n"), window->title, rect.rcWindow.right  - rect.rcWindow.left,
                                                                                                      rect.rcWindow.bottom - rect.rcWindow.top,
                                                                                                      rect.rcClient.right  - rect.rcClient.left,
                                                                                                      rect.rcClient.bottom - rect.rcClient.top);

  return true;
};


eTB_RenderWindow::~eTB_RenderWindow (void) {
  if (rc) {
    rc->Destroy ();

    delete rc;
    rc = NULL;
  }

  if (win) {
    win->Destroy ();

    ////delete win;
    win = NULL;
  }
}

bool
eTB_RenderWindow::OnResize (eWindowRect& rect) {
  viewport.Resize (rect.rcClient);

  int width  = viewport.Width  ();
  int height = viewport.Height ();

  TCHAR resize_str [64];
  _sntprintf (resize_str, 64, _T ("(%d x %d)"), width, height);

  // The Render Context may actually change the width/height...
  rc->Resize (width, height);

  viewport.Resize (width, height);

  if (states->input)
    states->input->Resize (width, height);

  // Determine the "center" position for the text...
  int iPosX, iPosY;

  eFont* font = states->r_font;

  iPosX = (width  / 2) - (int)(font->StringWidth  (resize_str) / 2.0f);
  iPosY = (height / 2) + (int)(font->StringHeight ()           / 2.0f);

  float OldClearColor [4];

  OldClearColor [0] = rc->GetClearColor ()[0];
  OldClearColor [1] = rc->GetClearColor ()[1];
  OldClearColor [2] = rc->GetClearColor ()[2];
  OldClearColor [3] = rc->GetClearColor ()[3];

  rc->SetClearColor (0.0f, 0.0f, 0.0f, 1.0f);

  rc->BeginFrame ();
    // We need to resize the viewport (in OpenGL) before drawing the text :)
    extern void SetupView (e3dViewport* viewport);
    SetupView (&viewport);

    color_printf (0xffff0000, iPosX, iPosY, _T ("%s"), resize_str);
  rc->EndFrame   ();

  /// TODO: Implement state stacks
  ///
  ///  * For now, this will restore the old clear color
  rc->SetClearColor (OldClearColor [0], OldClearColor [1],
                     OldClearColor [2], OldClearColor [3]);

  /* Last, update the cached value for window res x/y in the
       state machine... we could just as easily query the value
         from the window whenever we needed it, but this will make
           saving resolution configuration much simpler. */
  states->r_window_res_x = width;
  states->r_window_res_y = height;

  return true;
}

void
eTB_RenderWindow::Create (const TCHAR* name, int width, int height) {
/// XXX: Port Me !!!
#ifdef WIN32
  eWindowParametersWin32 params;

  params.instance      = NULL;
  params.window_proc   = NULL;
#else
  eWindowParametersQuartz params;
#endif
  params.window_height = height;
  params.window_width  = width;

  _tcscpy (params.window_name, name);

  win = epsilon.createWindow (&params);

  //win->setMoveCallback   (&WindowMove);
  //win->setResizeCallback (&WindowResize);

  win->installListener (this);

  win->ResizeClient (width, height);
  win->MoveClient   (32,  64);

  bool ret = false;

  rc  = new e3dRenderContext ();
  ret = rc->Create (win);

  if (ret) {
    eFontInfo info;
    info.szName         = _tcsdup (_T ("Arial"));
    info.numAlphaShades = 64;
    info.isBold         = false;
    info.isItalic       = false;
    info.isUnderline    = false;
    info.glyphSize      = 32;
    
    if (! states->r_font) {
      states->r_font = new eFont ();
      states->r_font->loadFont (&info);
    }

    /* Initialize the State Machine... this should be part of Epsilon3D! */

    // Hack, cannot change render states outside of Begin/EndFrame (...).
    rc->BeginFrame ();
//      rc->SetClearColor   (0.01f, 0.01f, 0.33f, 1.0f);
      rc->SetClearColor   (0.0f, 0.0f, 0.01f, 1.0f);
      rc->SetClearDepth   (1.0f);
      rc->SetClearStencil (0);

      rc->SetShadeModel   (e3dRenderStates::Smooth);
      rc->SetPerspective  (e3dRenderStates::Fastest);

      rc->SetDepthMask    (e3dRenderStates::True);
      rc->SetDepthTest    (e3dRenderStates::True);
      rc->SetDepthFunc    (e3dRenderStates::LessOrEqual);

      rc->SetBlend        (e3dRenderStates::False);
      rc->SetBlendFunc    (e3dRenderStates::One, e3dRenderStates::Zero);

      rc->SetColorMask    (e3dRenderStates::True, e3dRenderStates::True,
                           e3dRenderStates::True, e3dRenderStates::False);

      rc->SetAlphaFunc    (e3dRenderStates::Always, 0.0f);
      rc->SetAlphaTest    (e3dRenderStates::False);

      rc->SetFrontFace    (e3dRenderStates::CounterClockwise);

      rc->SetCullFaces    (e3dRenderStates::True);
      rc->SetCullSide     (e3dRenderStates::Back);

      rc->SetFillMode     (e3dRenderStates::Solid);

      /*
      rc->EnableTexture   (unit, e3dRenderStates::Tex2D);
      rc->DisableTexture  (unit, e3dRenderStates::Tex3D);
      rc->BindTexture     (unit, e3dRenderStates::Tex2D, texture);
      */

      rc->SetShadeModel   (e3dRenderStates::Smooth);
    rc->EndFrame ();
  }
}

void
eTB_RenderWindow::DrawFrame (void)
{
  states->HandleCamera ();
  states->TimedUpdates ();
  states->RenderFrame  ();

    /*
    static eFrameTimer timer;

    timer.Begin ();

    rc->BeginFrame ();
    {
      extern void SetupView (e3dViewport* viewport);
      SetupView (viewport);

      // Add Batch Transformations Here
      //
      ///e3dStart  (...);
      /// ...
      ///e3dFinish (...);

      //rc->QueueBatch  (...);
      // ...
      //
      //rc->SortBatches (...);
      //rc->DrawBatches (...);

      e3dLoadIdentity ();

      if (! states->DrawScene (rc, &viewport)) {
        states->DrawScene (rc, &viewport);
      }
    }
    rc->EndFrame ();

    timer.End ();
    */

/* Disabled, because this causes issues with hung application detection
     on Windows Vista / Windows 7... */
#if 0
#ifdef WIN32
  // Let the CPU rest a bit if we or our child windows are not the foreground window :)
  HWND hWndForeground = GetForegroundWindow ();
  if (hWndForeground != eHWND (win->getNativeWindowHandle ()))
    MsgWaitForMultipleObjects (0, 0, 0, 5, 0);
#endif
#endif
}

static e3dVertexCache* vcache = NULL;
static e3dIndexCache*  icache = NULL;

void
eTB_RenderWindow::printf (int x, int y, const TCHAR* szFormat, ...)
{
  eFont* font = states->r_font;

  if (! font)
    return;

  /* TODO: Get these from font->... */
  int pitch_x = 10;
  int pitch_y = 20;

#define OUT_BUF_SIZE 1024

  TCHAR szOut [OUT_BUF_SIZE];

  va_list    argv;

  va_start      (argv, szFormat);
    _vsntprintf (szOut, OUT_BUF_SIZE, szFormat, argv);
  va_end        (argv);

  if (x == -1)
    x = viewport.Width () - (pitch_x * _tcslen (szOut));

  if (y == -1)
    y = viewport.Height () - pitch_y;

#ifdef HAS_D3D
  D3DCOLOR fontColor = D3DCOLOR_ARGB (255,0,0,255);
#endif

  eRect rect;
  rect.left   = x;
  rect.right  = rect.left + (pitch_x * _tcslen (szOut));
  rect.top    = y;
  rect.bottom = rect.top + pitch_y;

#ifdef HAS_D3D
  // Draw some text
  if (states->r_font)
    states->r_font->DrawText (NULL, szOut, -1, &rect, 0, fontColor);
  //else
    //eTB_Printf (_T ("%s\n"), szOut);
#endif

  if (font) {
    if (! vcache)
      vcache = rc->CreateVertexCache ();

    if (! icache)
      icache = rc->CreateIndexCache ();

    font->Print (szOut, (float)rect.left, (float)rect.top, vcache, icache);
  }
}

void
eTB_RenderWindow::color_printf (DWORD fontColor, int x, int y, const TCHAR* szFormat, ...)
{
  eFont* font = states->r_font;

  if (! font)
    return;

#define OUT_BUF_SIZE 1024

  TCHAR szOut [OUT_BUF_SIZE];

  va_list    argv;

  va_start      (argv, szFormat);
    _vsntprintf (szOut, OUT_BUF_SIZE, szFormat, argv);
  va_end        (argv);

  if (! font) {
    eTB_Printf ("%s\n", szOut);
    return;
  }

  float str_width  = font->StringWidth  (szOut);
  float str_height = font->StringHeight ();

  if (x == -1)
    x = viewport.Width () - (int)str_width;

  if (y == -1)
    y = viewport.Height ();// - str_height;

  eRect rect;
  rect.left   = x;
  rect.right  = rect.left + (int)str_width;
  rect.top    = y;
  rect.bottom = rect.top  + (int)str_height;

  // Draw some text
  if (! vcache)
    vcache = rc->CreateVertexCache ();

  if (! icache)
    icache = rc->CreateIndexCache ();

#if 1 /// TODO: This is stupid!
  glColor4ubv ((unsigned char *)&fontColor);
#endif


  font->Print (szOut, (float)rect.left, (float)rect.top, vcache, icache);
}

void
eTB_RenderWindow::outline_printf (DWORD fontColor, int x, int y, const TCHAR* szFormat, ...)
{
  eFont* font = states->r_font;

  if (! font)
    return;

#define OUT_BUF_SIZE 1024

  TCHAR szOut [OUT_BUF_SIZE];

  va_list    argv;

  va_start      (argv, szFormat);
    _vsntprintf (szOut, OUT_BUF_SIZE, szFormat, argv);
  va_end        (argv);

  if (! font) {
    eTB_Printf ("%s\n", szOut);
    return;
  }

  float str_width  = font->StringWidth  (szOut);
  float str_height = font->StringHeight ();

  if (x == -1)
    x = viewport.Width () - (int)str_width;

  if (y == -1)
    y = viewport.Height ();// - str_height;

  eRect rect;
  rect.left   = x;
  rect.right  = rect.left + (int)str_width;
  rect.top    = y;
  rect.bottom = rect.top  + (int)str_height;

  // Draw some text
  if (! vcache)
    vcache = rc->CreateVertexCache ();

  if (! icache)
    icache = rc->CreateIndexCache ();

  // Print the black outline first
  //glColor4ub (0x00, 0x00, 0x00, 0xff);
  //font->Print (szOut, (float)rect.left, (float)rect.top, vcache, icache, 1.333f);

  // Next, the normal colored foreground text...
  glColor4ubv ((unsigned char *)&fontColor);
  font->Print (szOut, (float)rect.left, (float)rect.top, vcache, icache, 1.0f);
}

void
eTB_RenderWindow::color_print (DWORD fontColor, int x, int y, const TCHAR* szText, int length)
{
  eFont* font = states->r_font;

  if (! font)
    return;

  if (! font) {
    eTB_Printf ("%s\n", szText);
    return;
  }

  float str_width  = font->StringWidth  (szText);
  float str_height = font->StringHeight ();

  if (x == -1)
    x = viewport.Width () - (int)str_width;

  if (y == -1)
    y = viewport.Height ();// - str_height;

  eRect rect;
  rect.left   = x;
  rect.right  = rect.left + (int)str_width;
  rect.top    = y;
  rect.bottom = rect.top  + (int)str_height;

  // Draw some text
  if (! vcache)
    vcache = rc->CreateVertexCache ();

  if (! icache)
    icache = rc->CreateIndexCache ();

#if 1 /// TODO: This is stupid!
  char* color = (char *)&fontColor;
  glColor4ub (color [0], color [1], color [2], color [3]);
#endif

  font->Print (szText, (float)rect.left, (float)rect.top, vcache, icache);
}



///
/// Misc. Window Utilities
///

void
eTB_BringConsoleToFront (void)
{
#ifdef WIN32
  HWND hWndConsole = ::FindWindow (_T ("ConsoleWindowClass"), NULL);

  ::BringWindowToTop    (hWndConsole);
  ::SetForegroundWindow (hWndConsole);
  ::SetFocus            (hWndConsole);
#endif
}

void
eTB_BringWindowToFront (void)
{
#ifdef WIN32
  eHWND hWnd (states->window->win->getNativeWindowHandle ());

  ::BringWindowToTop    (hWnd);
  ::SetForegroundWindow (hWnd);
  ::SetFocus            (hWnd);
#endif
}

// See states.cpp
template <typename T>
extern
void
SetVarVal (T, eTB_Variable* stub, void* val);

bool
eTB_RenderWindow::OnVarChange (eTB_Variable* var, void* val)
{
  int x = 0;

  // When the window res changes, resize the viewport...
  if (((eTB_VarStub <int>*) var)->getValuePtr () == &states->r_window_res_x || 
      ((eTB_VarStub <int>*) var)->getValuePtr () == &states->r_window_res_y) {
    if (::states->window != NULL) {
      SetVarVal (x, var, val);

      win->Resize (states->r_window_res_x, states->r_window_res_y);

      return true;
    }
  }

  return false;
}